import {
  canvasRGB,
} from 'stackblur'
import { api as qrcodeApi } from 'qrcode'
import type { ArcType, BlurType, CreateCanvasOpt, DrawType, ImageType, LineStyle, LineType, QrcodeType, RectType, TextType } from './index.d.js'

const SYS_INFO = uni.getSystemInfoSync()
class UniWxCanvas2d {
  query: string
  bgColor: string | undefined
  radius: number | undefined
  component: any
  canvas: any
  canvasInfo: any
  ctx: any
  dpr: number
  startTime: number | undefined
  rootWidth: number
  listener: any
  constructor() {
    this.query = '' // query查询条件
    this.bgColor = undefined // canvas 背景色
    this.radius = 0 // canvas 圆角
    this.component = undefined // 如果是在自定义组件中，需要获取到自定义组件的内部 this 变量 (即，传入 this)
    this.canvas = {} // canvas 节点
    this.canvasInfo = undefined //  wx.createSelectorQuery 返回的 res
    this.rootWidth = 0 // 参考设备宽度 (即开发时UI设计稿的宽度，默认375，可改为750)
    this.ctx = {} // canvas 上下文
    this.dpr = 0 // 像素比
    this.startTime = 0 // 绘图开始时间戳
    this.listener = {} // 监听回调
  }

  // canvas 画布大小 (单位: rpx)
  get canvasSize() {
    if (!this.canvas || !this.dpr || !this.rootWidth) {
      return {
        width: 0,
        height: 0,
      }
    }
    return {
      width:
        (this.canvas.width / SYS_INFO.screenWidth / this.dpr) * this.rootWidth,
      height:
        (this.canvas.height / SYS_INFO.screenWidth / this.dpr) * this.rootWidth,
    }
  }

  // 通过 wx.createSelectorQuery 获取 canvas 元素
  queryCanvas() {
    return new Promise((resolve) => {
      (this.component || uni).createSelectorQuery()
        .select(this.query)
        .fields({ node: true, size: true }, () => { })
        .exec((res) => {
          if (res[0]) {
            resolve(res)
          }
        })
    })
  }

  // 创建画布
  create(opts: CreateCanvasOpt) {
    return new Promise((resolve, reject) => {
      const options = {
        query: '',
        rootWidth: 375,
        ...opts,
      }

      if (!options.query)
        reject(new Error('[WxCanvas2d] \'query\' is empty.'))

      this.query = options.query
      this.bgColor = options.bgColor
      this.component = options.component
      this.radius = options.radius
      this.queryCanvas().then((res) => {
        const canvas = res[0].node
        const ctx = canvas.getContext('2d')
        const dpr = SYS_INFO.pixelRatio
        this.canvas = res[0].node
        this.canvasInfo = res
        this.ctx = ctx
        this.dpr = dpr
        this.rootWidth = options.rootWidth
        this.canvas.width = res[0].width * this.dpr
        this.canvas.height = res[0].height * this.dpr
        resolve('')
      }).catch((err) => {
        reject(err)
      })
    })
  }

  // 清空画布
  clear() {
    // 画布范围
    const scope = [
      0,
      0,
      this.xDpr(this.canvasSize.width),
      this.xDpr(this.canvasSize.height),
    ]
    // 清空画布
    this.ctx.clearRect(...scope)
    // 设置圆角路径并剪切
    if (this.radius) {
      this.drawRectPath({
        x: 0,
        y: 0,
        width: this.canvasSize.width,
        height: this.canvasSize.height,
        radius: this.radius,
      })

      // 剪切路径，上下文没有保存和还原设置，所以后续所有绘制都会被限制在此剪切范围内
      this.ctx.clip()
    }

    // 绘制背景色
    if (this.bgColor) {
      this.ctx.fillStyle = this.bgColor
      this.ctx.fillRect(...scope)
    }
  }

  // canvas 大小适配
  xDpr(val: number) {
    if (!this.dpr || !this.rootWidth) {
      return val
    }
    return val * this.dpr * SYS_INFO.screenWidth / this.rootWidth
  }

  // 绘制矩形路径
  drawRectPath(opts: RectType) {
    const {
      x = 0,
      y = 0,
      width = 0,
      height = 0,
    } = opts
    const maxRadius = Math.min(width, height) / 2
    const radius = Math.max(0, Math.min(opts.radius || 0, maxRadius))
    const angle = {
      top: Math.PI * 1.5,
      right: 0,
      bottom: Math.PI * 0.5,
      left: Math.PI,
    }
    const angleArr = [
      [angle.left, angle.top],
      [angle.top, angle.right],
      [angle.right, angle.bottom],
      [angle.bottom, angle.left],
    ]
    const arcPos = [
      [x + radius, y + radius].map(n => this.xDpr(n)), // left top
      [x + width - radius, y + radius].map(n => this.xDpr(n)), // top right
      [x + width - radius, y + height - radius].map(n => this.xDpr(n)), // right bottom
      [x + radius, y + height - radius].map(n => this.xDpr(n)), // bottom left
    ]
    this.ctx.beginPath()
    arcPos.forEach((n, i) => {
      this.ctx.arc(...n, this.xDpr(radius), ...angleArr[i])
    })
    this.ctx.closePath()
  }

  // 设置线的样式
  setLineStyle(opts: LineStyle) {
    if (Object.keys(opts).length === 0)
      return
    const {
      cap = 'butt',
      join = 'bevel',
      offset = 0,
      dash = [1, 0],
      color = '#000',
      width = 2,
    } = opts ?? {}
    this.ctx.lineCap = cap
    this.ctx.setLineDash(dash.map(n => this.xDpr(n)))
    this.ctx.lineDashOffset = this.xDpr(offset)
    this.ctx.lineJoin = join
    this.ctx.lineWidth = this.xDpr(width)
    this.ctx.strokeStyle = color
  }

  draw(opts: DrawType) {
    return new Promise((resolve, reject) => {
      this.startTime = Date.now()
      this.queryCanvas().then((res: any) => {
        this.canvas.width = res[0].width * this.dpr
        this.canvas.height = res[0].height * this.dpr
        this.clear()
        const list = opts?.list?.map(n => ({ ...n, zIndex: n.zIndex || 0 })).sort((n, m) => n.zIndex - m.zIndex) || []
        // 按顺序绘制图层方法
        const next = (index = 0) => {
          const nextStart = Date.now()
          const config = list[index]
          this.ctx.save()
          const drawMethods = new Map<string, Function>([
            ['rect', this.drawRect.bind(this)],
            ['arc', this.drawArc.bind(this)],
            ['blur', this.drawBlur.bind(this)],
            ['image', this.drawImage.bind(this)],
            ['line', this.drawLine.bind(this)],
            ['qrcode', this.drawQrcode.bind(this)],
            ['text', this.drawText.bind(this)],
          ])
          if (index < list.length) {
            const method = drawMethods.get(config.type || '')
            if (method) {
              this.emit('beforeDraw', { index, config })
              method(config).then(() => {
                next(++index)
              }).catch((error: any) => {
                reject(error)
              }).finally(() => {
                this.emit('afterDraw', { index, config })
              })
            }
            else {
              next(++index)
            }
          }
          else {
            // 所有图层绘制完毕
            resolve('')
          }
          this.ctx.restore()
        }
        next()
      }).catch((err) => {
        reject(err)
      })
    })
  }

  // 弧线
  drawArc(opts: ArcType) {
    return new Promise((resolve) => {
      const {
        x = 0,
        y = 0,
        r = 0,
        start = 0,
        end = 0,
        reverse = false,
        lineStyle = {},
      } = opts
      this.setLineStyle(lineStyle)
      this.ctx.beginPath()
      this.ctx.arc(
        this.xDpr(x),
        this.xDpr(y),
        this.xDpr(r),
        start,
        end,
        reverse,
      )
      this.ctx.stroke()
      resolve('')
    })
  }

  // 高斯模糊
  drawBlur(opts: BlurType) {
    return new Promise((resolve) => {
      const {
        x = 0,
        y = 0,
        width = 0,
        height = 0,
        blur = 0,
      } = opts
      canvasRGB(
        this.canvas,
        this.xDpr(x),
        this.xDpr(y),
        this.xDpr(width),
        this.xDpr(height),
        blur,
      )
      resolve('')
    })
  }

  // 绘制图片
  drawImage(opts: ImageType) {
    return new Promise((resolve, reject) => {
      const {
        url = '',
        x = 0,
        y = 0,
        width = 0,
        height = 0,
        mode = 'scaleToFill',
        radius = 0,
      } = opts
      const img = this.canvas.createImage()
      img.src = url
      img.onload = () => {
        uni.getImageInfo({
          src: url,
          success: (res: any) => {
            const imgWidth = res.width
            const imgHeight = res.height
            const aspectRatio = width / height
            let widthRatio = 1
            let heightRatio = 1
            // 原图等比例缩放后截取范围的长宽比
            if (mode === 'aspectFit') {
              widthRatio = imgWidth / imgHeight < aspectRatio
                ? width / imgWidth * imgHeight / height
                : 1
              heightRatio = imgWidth / imgHeight > aspectRatio
                ? height / imgHeight * imgWidth / width
                : 1
            }
            else if (mode === 'aspectFill') {
              widthRatio = imgWidth / imgHeight > aspectRatio
                ? width / imgWidth * imgHeight / height
                : 1
              heightRatio = imgWidth / imgHeight < aspectRatio
                ? height / imgHeight * imgWidth / width
                : 1
            }
            const imgCut: Record<string, number[]> = {
              'scaleToFill': [
                0,
                0,
                imgWidth,
                imgHeight,
              ], // 缩放: 不保持纵横比缩放图片，使图片的宽高完全拉伸至填满 image 元素
              'aspectFit': [
                (res.width - res.width * widthRatio) / 2,
                (res.height - res.height * heightRatio) / 2,
                res.width * widthRatio,
                res.height * heightRatio,
              ], // 缩放: 保持纵横比缩放图片，使图片的长边能完全显示出来。也就是说，可以完整地将图片显示出来。
              'aspectFill': [
                (res.width - res.width * widthRatio) / 2,
                (res.height - res.height * heightRatio) / 2,
                res.width * widthRatio,
                res.height * heightRatio,
              ], // 缩放: 保持纵横比缩放图片，只保证图片的短边能完全显示出来。也就是说，图片通常只在水平或垂直方向是完整的，另一个方向将会发生截取。
              'widthFix': [], // 缩放: 宽度不变，高度自动变化，保持原图宽高比不变
              'top': [
                (imgWidth - width) / 2,
                0,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的顶部区域
              'bottom': [
                (imgWidth - width) / 2,
                imgHeight - height,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的底部区域
              'center': [
                (imgWidth - width) / 2,
                (imgHeight - height) / 2,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的中间区域
              'left': [
                0,
                (imgHeight - height) / 2,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的左边区域
              'right': [
                imgWidth - width,
                (imgHeight - height) / 2,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的右边区域
              'top left': [
                0,
                0,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的左上边区域
              'top right': [
                imgWidth - width,
                0,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的右上边区域
              'bottom left': [
                0,
                imgHeight - height,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的左下边区域
              'bottom right': [
                imgWidth - width,
                imgHeight - height,
                width,
                height,
              ], // 裁剪: 不缩放图片，只显示图片的右下边区域
            }
            if (radius) {
              this.ctx.save()
              this.drawRectPath({ x, y, width, height, radius })
              this.ctx.clip()
            }
            this.ctx.drawImage(
              img,
              ...(imgCut[mode] || []),
              this.xDpr(x) || 0,
              this.xDpr(y) || 0,
              this.xDpr(width || res.width),
              this.xDpr(height || res.height),
            )
            if (radius) {
              this.ctx.restore()
            }
            resolve('')
          },
        })
      }
      img.onerror = (err: any) => {
        reject(err)
      }
    })
  }

  // 绘制线段
  drawLine(opts: LineType) {
    return new Promise((resolve) => {
      const {
        lineStyle,
        line = [],
      } = opts
      if (lineStyle) {
        this.setLineStyle(lineStyle)
      }
      line.forEach((n, i) => {
        if (!i) {
          this.ctx.beginPath()
          this.ctx.moveTo(...n.point.map(n => this.xDpr(n)))
        }
        else {
          this.ctx.lineTo(...n.point.map(n => this.xDpr(n)))
          this.ctx.stroke()
        }
      })
      resolve('')
    })
  }

  // 绘制二维码
  drawQrcode(opts: QrcodeType) {
    return new Promise((resolve) => {
      const {
        text = '',
        x = 0,
        y = 0,
        size = 0,
        color = '#000',
        bgColor = '#fff',
        ecc = 2,
      } = opts
      qrcodeApi.draw(
        text,
        this.ctx,
        this.xDpr(x),
        this.xDpr(y),
        this.xDpr(size), // width
        this.xDpr(size), // height
        bgColor,
        color,
        this.component,
        ecc,
      )
      resolve('')
    })
  }

  // 绘制矩形
  drawRect(opts: RectType) {
    return new Promise((resolve) => {
      let {
        x = 0,
        y = 0,
        width = 0,
        height = 0,
        bgColor = '',
        radius = 0,
        lineStyle = {},
      } = opts
      // 防止 radius 设置过大
      radius = Math.min(radius, Math.min(width, height) / 2)
      // 设置线段样式
      this.setLineStyle(lineStyle)
      // 设置填充色
      this.ctx.fillStyle = bgColor
      this.drawRectPath({
        x,
        y,
        width,
        height,
        radius,
      })

      if (lineStyle.color) {
        this.ctx.stroke()
      }

      if (bgColor) {
        this.ctx.fill()
      }

      resolve('')
    })
  }

  drawText(opts: TextType) {
    return new Promise((resolve) => {
      const {
        x = 0,
        y = 0,
        color = '#000',
        fontSize = 12,
        fontWeight = '',
        width = Infinity,
        baseline = 'top', // top | hanging | middle | alphabetic | ideographic | bottom
        align = 'left', // left | right | center | start | end
        text = '',
        ellipsis = 0,
        lineHeight = opts.fontSize || 12,
        fontFamily = 'sans-serif',
      } = opts
      let index = 0 // 行数下标
      let splitStr: any = [] // 拆分后的文本数组
      this.ctx.textAlign = align
      this.ctx.textBaseline = baseline
      this.ctx.fillStyle = color
      this.ctx.font = `${fontWeight} ${this.xDpr(fontSize)}px ${fontFamily}`
      // 拆分文本
      Array.from(text).forEach((n, i) => {
        let start = 0 // 截取的起始下标

        String(n).split('').forEach((m, j) => {
          const str = String(n).slice(start, j + 1)

          if (this.ctx.measureText(str).width < this.xDpr(width)) {
            splitStr[index] = str
          }
          else {
            splitStr[index + 1] = m // 显示不下多出来的那个字存到下一行
            start = j
            index++
          }
        })

        index++
      })
      // 最大显示行，超出显示省略号
      if (ellipsis && splitStr.length > ellipsis) {
        splitStr = splitStr.slice(0, ellipsis)
        splitStr[ellipsis - 1] = `${splitStr[ellipsis - 1].slice(0, -1)}...`
      }
      // 循环绘制文本
      splitStr.forEach((n: string, i: number) => {
        this.ctx.fillText(
          n,
          this.xDpr({
            left: x,
            start: x,
            right: x + width,
            end: x + width,
            center: x + width / 2,
          }[align] || x),
          this.xDpr(y + lineHeight * i + (lineHeight - fontSize) / 2),
        )
      })
      resolve('')
    })
  }

  // 事件监听
  on(evt: any, cb: Function) {
    if (this.listener[evt]) {
      if (!this.listener[evt].includes(cb))
        this.listener[evt].push(cb)
    }
    else {
      this.listener[evt] = [cb]
    }
  }

  // 取消监听
  off(evt: any, cb: Function) {
    const fns = this.listener[evt]

    if (fns) {
      if (cb) {
        fns.some((fn: any, idx: number) => fn === cb && delete this.listener[evt][idx])
      }
      else {
        delete this.listener[evt]
      }
    }
  }

  // 触发监听事件回调
  emit(evt: any, pars: any) {
    if (Array.isArray(this.listener[evt])) {
      this.listener[evt].forEach((n: any) => n({
        event: evt,
        ...pars,
      }))
    }
  }
}

export default UniWxCanvas2d
